<?php

namespace app\service;

use Exception;
use GuzzleHttp\Client;

class AlipayService
{
    private $config;
    private $client;

    public function __construct()
    {
        $this->config = config('oauth.alipay');
        $this->client = new Client([
            'timeout' => 10,
            'verify' => false,
        ]);
    }

    /**
     * 获取用户信息
     */
    public function getUserInfo($authCode)
    {
        // 1. 通过授权码获取access_token
        $tokenData = $this->getAccessToken($authCode);

        // 2. 通过access_token获取用户信息
        $userInfo = $this->getUserInfoByToken($tokenData['access_token']);

        return [
            'openid' => $tokenData['user_id'],
            'unionid' => '', // 支付宝没有unionid概念
            'nickname' => $userInfo['nick_name'] ?? '',
            'avatar' => $userInfo['avatar'] ?? '',
            'access_token' => $tokenData['access_token'],
            'refresh_token' => $tokenData['refresh_token'],
            'expires_at' => date('Y-m-d H:i:s', time() + $tokenData['expires_in']),
            'platform' => 'alipay',
            'extra_data' => [
                'real_name' => $userInfo['real_name'] ?? '',
                'cert_no' => $userInfo['cert_no'] ?? '',
                'gender' => $userInfo['gender'] ?? '',
                'province' => $userInfo['province'] ?? '',
                'city' => $userInfo['city'] ?? '',
            ]
        ];
    }

    /**
     * 通过授权码获取access_token
     */
    private function getAccessToken($authCode)
    {
        $params = [
            'method' => 'alipay.system.oauth.token',
            'app_id' => $this->config['app_id'],
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'grant_type' => 'authorization_code',
            'code' => $authCode,
        ];

        // 生成签名
        $params['sign'] = $this->generateSign($params);

        $response = $this->client->post('https://openapi.alipay.com/gateway.do', [
            'form_params' => $params
        ]);

        $result = json_decode($response->getBody()->getContents(), true);

        if (isset($result['alipay_system_oauth_token_response']['error_response'])) {
            $error = $result['alipay_system_oauth_token_response']['error_response'];
            throw new Exception('支付宝授权失败: ' . ($error['sub_msg'] ?? $error['msg']));
        }

        return $result['alipay_system_oauth_token_response'];
    }

    /**
     * 通过access_token获取用户信息
     */
    private function getUserInfoByToken($accessToken)
    {
        $params = [
            'method' => 'alipay.user.info.share',
            'app_id' => $this->config['app_id'],
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'auth_token' => $accessToken,
        ];

        // 生成签名
        $params['sign'] = $this->generateSign($params);

        $response = $this->client->post('https://openapi.alipay.com/gateway.do', [
            'form_params' => $params
        ]);

        $result = json_decode($response->getBody()->getContents(), true);

        if (isset($result['alipay_user_info_share_response']['error_response'])) {
            $error = $result['alipay_user_info_share_response']['error_response'];
            throw new Exception('获取用户信息失败: ' . ($error['sub_msg'] ?? $error['msg']));
        }

        return $result['alipay_user_info_share_response'];
    }

    /**
     * 生成签名
     */
    private function generateSign($params)
    {
        // 排除签名字段
        unset($params['sign']);

        // 按键名排序
        ksort($params);

        // 拼接待签名字符串
        $stringToBeSigned = '';
        foreach ($params as $key => $value) {
            if (!empty($value)) {
                $stringToBeSigned .= $key . '=' . $value . '&';
            }
        }
        $stringToBeSigned = rtrim($stringToBeSigned, '&');

        // RSA2签名
        $privateKey = $this->formatPrivateKey($this->config['private_key']);
        openssl_sign($stringToBeSigned, $sign, $privateKey, OPENSSL_ALGO_SHA256);

        return base64_encode($sign);
    }

    /**
     * 验证签名
     */
    private function verifySign($params, $sign)
    {
        // 排除签名字段
        unset($params['sign']);
        unset($params['sign_type']);

        // 按键名排序
        ksort($params);

        // 拼接待验证字符串
        $stringToBeVerified = '';
        foreach ($params as $key => $value) {
            if (!empty($value)) {
                $stringToBeVerified .= $key . '=' . $value . '&';
            }
        }
        $stringToBeVerified = rtrim($stringToBeVerified, '&');

        // 验证签名
        $publicKey = $this->formatPublicKey($this->config['alipay_public_key']);
        $result = openssl_verify($stringToBeVerified, base64_decode($sign), $publicKey, OPENSSL_ALGO_SHA256);

        return $result === 1;
    }

    /**
     * 格式化私钥
     */
    private function formatPrivateKey($privateKey)
    {
        if (strpos($privateKey, '-----BEGIN') === false) {
            $privateKey = "-----BEGIN RSA PRIVATE KEY-----\n" .
                chunk_split($privateKey, 64, "\n") .
                "-----END RSA PRIVATE KEY-----";
        }
        return $privateKey;
    }

    /**
     * 格式化公钥
     */
    private function formatPublicKey($publicKey)
    {
        if (strpos($publicKey, '-----BEGIN') === false) {
            $publicKey = "-----BEGIN PUBLIC KEY-----\n" .
                chunk_split($publicKey, 64, "\n") .
                "-----END PUBLIC KEY-----";
        }
        return $publicKey;
    }

    /**
     * 刷新access_token
     */
    public function refreshAccessToken($refreshToken)
    {
        $params = [
            'method' => 'alipay.system.oauth.token',
            'app_id' => $this->config['app_id'],
            'charset' => 'utf-8',
            'sign_type' => 'RSA2',
            'timestamp' => date('Y-m-d H:i:s'),
            'version' => '1.0',
            'grant_type' => 'refresh_token',
            'refresh_token' => $refreshToken,
        ];

        // 生成签名
        $params['sign'] = $this->generateSign($params);

        $response = $this->client->post('https://openapi.alipay.com/gateway.do', [
            'form_params' => $params
        ]);

        $result = json_decode($response->getBody()->getContents(), true);

        if (isset($result['alipay_system_oauth_token_response']['error_response'])) {
            $error = $result['alipay_system_oauth_token_response']['error_response'];
            throw new Exception('刷新Token失败: ' . ($error['sub_msg'] ?? $error['msg']));
        }

        return $result['alipay_system_oauth_token_response'];
    }
}